<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>梦幻数生长动画</title>
  <style type="text/css">
    body {
      padding: 0;
      margin: 0;
      background: #fff;
      font-family: Courier;
    }

    canvas {
      background-color: #000;
      cursor: pointer;
    }
  </style>
</head>

<body>
<canvas id="canvas"></canvas>
<span id="statMsg" style="display:none;font-size: 30px"></span>
</body>

<script type="text/javascript">
  // 生成指定范围的随机数
  function rnd(min, max) {
    return min + Math.floor(Math.random() * (max - min + 1));
  }

  var rid;

  function init() {
    var Vector = function (x, y) {
      this.x = x || 0;
      this.y = y || 0;
    };
    Vector.prototype = {
      add: function (v) {
        this.x += v.x;
        this.y += v.y;
        return this;
      },
      length: function () {
        return Math.sqrt(this.x * this.x + this.y * this.y);
      },
      rotate: function (theta) {
        var x = this.x;
        var y = this.y;
        this.x = Math.cos(theta) * this.x - Math.sin(theta) * this.y;
        this.y = Math.sin(theta) * this.x + Math.cos(theta) * this.y;
        return this;
      },
      mult: function (f) {
        this.x *= f;
        this.y *= f;
        return this;
      },
    };
    var Leaf = function (p, r, c, ctx) {
      this.p = p || null;
      this.r = r || 0;
      this.c = c || 'rgba(255,255,255,1.0)';
      this.ctx = ctx;
    };
    Leaf.prototype = {
      render: function () {
        var that = this;
        var ctx = this.ctx;
        var f = Branch.random(1, 2);
        for (var i = 0; i < 5; i++) {
          (function (r) {
            setTimeout(function () {
              // 绘制树梢光点
              ctx.beginPath();
              ctx.fillStyle = that.color;
              ctx.moveTo(that.p.x, that.p.y);
              ctx.arc(that.p.x, that.p.y, r, 0, Branch.circle, true);
              ctx.fill();
            }, r * 60);
          })(i);
        }
      },
    };
    var Branch = function (p, v, r, c, t) {
      this.p = p || null;
      this.v = v || null;
      this.r = r || 0;
      this.length = 0;
      this.generation = 1;
      this.tree = t || null;
      this.color = c || 'rgba(255,255,255,1.0)';
      this.register();
    };
    Branch.prototype = {
      register: function () {
        this.tree.addBranch(this);
      },
      draw: function () {
        // 绘制主干
        var ctx = this.tree.ctx;
        ctx.beginPath();
        ctx.fillStyle = this.color;
        ctx.moveTo(this.p.x, this.p.y);
        ctx.arc(this.p.x, this.p.y, this.r, 0, Branch.circle, true);
        ctx.shadowBlur = this.r;
        ctx.shadowColor = this.color;
        ctx.fill();
      },
      modify: function () {
        var angle = 0.18 - 0.1 / this.generation;

        this.p.add(this.v);
        this.length += this.v.length();
        this.r *= 0.99;
        this.v.rotate(Branch.random(-angle, angle)); //.mult(0.996);
        // 如果树干半径过小 || 树干分叉超过10次
        if (this.r < 0.8 || this.generation > 10) {
          this.tree.removeBranch(this);
          var l = new Leaf(this.p, 10, this.color, this.tree.ctx);
          l.render();
        }
      },
      grow: function () {
        this.draw();
        this.modify();
        this.fork();
      },
      fork: function () {
        var p = this.length - Branch.random(100, 200); // + (this.generation * 10);
        if (p > 0) {
          var n = Math.round(Branch.random(1, 3));
          this.tree.stat.fork += n - 1;

          for (var i = 0; i < n; i++) {
            Branch.clone(this);
          }
          this.tree.removeBranch(this);
        }
      },
    };
    Branch.circle = 2 * Math.PI;
    Branch.random = function (min, max) {
      return Math.random() * (max - min) + min;
    };
    Branch.clone = function (b) {
      var r = new Branch(new Vector(b.p.x, b.p.y), new Vector(b.v.x, b.v.y), b.r, b.color, b.tree);
      r.generation = b.generation + 1;
      return r;
    };
    Branch.rgba = function (r, g, b, a) {
      return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
    };
    Branch.randomrgba = function (min, max, a) {
      return Branch.rgba(
        Math.round(Branch.random(min, max)),
        Math.round(Branch.random(min, max)),
        Math.round(Branch.random(min, max)),
        a
      );
    };
    var Tree = function () {
      var branches = [];
      this.stat = {
        fork: 0,
        length: 0,
      };
      this.addBranch = function (b) {
        branches.push(b);
      };
      this.removeBranch = function (b) {
        for (var i = 0; i < branches.length; i++) {
          if (branches[i] === b) {
            branches.splice(i, 1);
            return;
          }
        }
      };
      this.render = function (fn) {
        var that = this;

        function draw_tree() {
          rid = window.requestAnimationFrame(draw_tree);
          fn.apply(that, arguments);
          if (branches.length > 0) {
            for (var i = 0; i < branches.length; i++) {
              branches[i].grow();
            }
          } else {
            // 结束
            window.cancelAnimationFrame(rid);
          }
        }

        draw_tree()
      };
      this.init = function (ctx) {
        this.ctx = ctx;
      };
      this.abort = function () {
        branches = [];
        this.stat = {
          fork: 0,
          length: 0,
        };
      };
    };
    // init
    var canvas_width = window.innerWidth;
    var canvas_height = window.innerHeight;
    var center_x = canvas_width / 2;
    var stretch_factor = 600 / canvas_height;

    var y_speed = 3 / stretch_factor;
    // tx
    var canvas = document.getElementById('canvas');
    canvas.width = canvas_width;
    canvas.height = canvas_height;
    var ctx = canvas.getContext('2d');
    ctx.globalCompositeOperation = 'lighter';
    // tree
    var t = new Tree();
    t.init(ctx);
    // 生成主干的数量
    for (var i = 0; i < rnd(4, 8); i++) {
      var a = new Branch(
        new Vector(center_x, canvas_height),
        new Vector(Math.random(-1, 1), -y_speed),
        15 / stretch_factor,
        Branch.randomrgba(0, 255, 0.3),
        t
      );
    }
    t.render(function () {
      document.getElementById('statMsg').innerHTML = this.stat.fork;
    });
  }

  init();
  window.addEventListener('resize', function () {
    window.cancelAnimationFrame(rid);
    init();
  });
</script>
</html>
